@colixsystems/widget-sdk 0.277.1 → 0.277.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,7 +63,8 @@ The data layer lives in **four separate domain-client packages**, each instantia
63
63
  | **DATASTORE** | `useTyping(tableId, recordId)` | `{ typingUserIds, setTyping, status }` — status `"connecting" \| "live" \| "unavailable"` | `records(table).typing` — `datastore.read:<table>`. Who else is typing in one record, over the same socket as `useDatastoreSubscription`. Call `setTyping(true)` on every keystroke (throttled to one signal per 3 s; `false` is sent after 5 s idle) and `setTyping(false)` on send, clear or blur. `typingUserIds` lists the OTHER users' ids; each drops out 6 s after their last signal. Needs a signed-in user who can read the record. Never throws: an anonymous visitor, a refused subscription or a host whose client lacks `typing` reads `{ typingUserIds: [], status: "unavailable" }`. Re-subscribes when either id changes. |
64
64
  | **DATASTORE** | `useRecordPermissions(tableId, recordId)` | `{ permissions, loading, error, grant, revoke, update, refetch }` | `records(table).permissions(record).{ list, grant, update, revoke }` — `acl.write:records` (+ `can_grant` on the record) |
65
65
  | **DATASTORE** | `useCanWrite(tableId, options)` (optional: options) | `{ canWrite, loading, error, refetch }` | `myPermissions(tableId, { recordId? })` — scope `datastore.read:<table>`. A FLOOR, not a full replacement for domain-specific write rules: answers "may this caller write", reading the same table-ACL answer the write endpoint enforces — so a table granting Create to Everyone answers `true` for a logged-out visitor, and this hook alone is the right gate for a widget meant to work without signing in. Pass `{ recordId }` for a per-row check. A widget whose own rule is MORE SPECIFIC than the table ACL (e.g. "only the assigned user may edit this row") must still hand-check that in addition. Pair with `useUser()` to also tell "not signed in" apart from "signed in but forbidden" — both resolve `canWrite: false` here. Falsy `tableId`, or a host that hasn't injected `myPermissions` (an older host), collapses to `{ canWrite: false, loading: false, error: null, refetch: async () => undefined }` rather than throwing. |
66
- | **FILES** (`ctx.assets`) | `useAsset(id)` | `{ url, asset, loading, error, refetch }` | `ctx.assets.get` — no scope |
66
+ | **FILES** (`ctx.filestore`) | `useFilestoreFile(fileId)` | `{ file, url, urls, loading, error, refetch }` | `ctx.filestore.files.get` — `files.read:*`. **A FILE column's value is a filestore file id.** Draw it with `<FileImage fileId={row.photo} variant="card" />`, or read `useFilestoreFile(row.photo).url` (`urls.thumbnail` / `.card` / `.large` for a smaller render); `url` and `urls` are `null` until the fetch resolves and for an empty cell. Never pass a column value to `useAsset`: it resolves no file id, so the picture renders blank. |
67
+ | **FILES** (`ctx.assets`) | `useAsset(id)` | `{ url, asset, loading, error, refetch }` | `ctx.assets.get` — no scope. Takes an asset-library id: an `asset` property's value, or the `asset_id` an image import (`import_image`, `import_stock_image`) answers. Never a FILE column's value, which is a filestore file id (see `useFilestoreFile`). |
67
68
  | **FILES** | `useAssetsByTag(tag, { type })` (optional: the whole argument, and `type` within it) | `{ assets, loading, error, refetch }` | `ctx.assets.list` (unwraps `{ data, meta }` to `assets`) — no scope. `type` defaults to `"image"`; pass `"all"` / `"audio"` / `"video"` / `"document"` to widen. Falsy `tag` collapses to `assets: []` without a round-trip. |
68
69
  | **FILES** (`ctx.widget`) | `useBundledAsset(fileName)` | `string \| null` | `ctx.widget.assets` — no scope. The URL of an image or sound the widget package ships in its `assets/` directory, or `null` when it has no file with that name. See [Bundled images and sounds](#bundled-images-and-sounds). |
69
70
  | **DIRECTORY** (`ctx.directory`) | `useDirectory(query)` (optional: query) | `{ users, loading, error, refetch }` | `directory.users.list` — `directory.read:users` |
@@ -86,7 +87,15 @@ See the design reference for the full architecture: [`docs/architecture/widget-m
86
87
 
87
88
  ## Status
88
89
 
89
- `v0.277.1` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
90
+ `v0.277.3` — pre-publish. The package surface (types, function names, export paths) is the v1 contract; runtime behaviour for some hooks is stubbed (each hook documents what's wired and what isn't). It is **not yet published to npm**.
91
+
92
+ ### What's new in 0.277.3
93
+
94
+ **A FILE column's value goes to `<FileImage fileId>` or `useFilestoreFile`, never `useAsset`.** The hook table's FILES group now leads with the filestore file id a FILE column holds and says that `useAsset(id)` takes an asset-library id. A new linter warning, `useAsset-on-file-column`, flags `useAsset(row.photo)` (a value read from a datastore row) in a widget that reads records: the hook resolves no URL for a file id, so the picture renders blank.
95
+
96
+ ### What's new in 0.277.2
97
+
98
+ **`<MarkdownInput>` shows list numbers and bullets on web.** The editor declares its own list markers, so a host page whose stylesheet resets list styles no longer hides them.
90
99
 
91
100
  ### What's new in 0.277.1
92
101
 
package/dist/linter.cjs CHANGED
@@ -1584,6 +1584,32 @@ function _rawUseMemoIntoDatastoreQueryRules(source) {
1584
1584
  return findings;
1585
1585
  }
1586
1586
 
1587
+ // A FILE column holds a filestore file id; useAsset resolves asset-library
1588
+ // ids only, so a row value passed to it renders a blank picture.
1589
+ function _useAssetOnFileColumnRules(source) {
1590
+ const code = _stripNonCode(source);
1591
+ if (!/\buseDatastore(?:Query|Record)\w*\s*\(/.test(code)) return [];
1592
+ const callRe = /\buseAsset\s*\(\s*(?:row|record|item)\s*\??\s*[.[]/g;
1593
+ const sourceLines = source.split(/\r?\n/);
1594
+ const lineAt = _lineCounter(code);
1595
+ const findings = [];
1596
+ let m;
1597
+ while ((m = callRe.exec(code))) {
1598
+ const line = lineAt(m.index);
1599
+ findings.push({
1600
+ rule: "useAsset-on-file-column",
1601
+ severity: "warning",
1602
+ label:
1603
+ "useAsset() resolves an asset-library id, never a FILE column's value: " +
1604
+ "that is a filestore file id, so draw it with <FileImage fileId={…}> " +
1605
+ "or read useFilestoreFile(id).url.",
1606
+ line,
1607
+ snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
1608
+ });
1609
+ }
1610
+ return findings;
1611
+ }
1612
+
1587
1613
  // sc-5206 — soft warning, the write-gate twin of `write-not-gated-on-user`.
1588
1614
  // A widget that hand-derives write permission from `useUser().groupIds` /
1589
1615
  // `.roles` re-implements the table ACL client-side and drifts from it the
@@ -2874,6 +2900,7 @@ function lintSource(source, options) {
2874
2900
  // sc-5206 — soft warnings steering a widget toward the two new hooks.
2875
2901
  findings.push(..._rawUseMemoIntoDatastoreQueryRules(source));
2876
2902
  findings.push(..._handRolledWriteGateRules(source));
2903
+ findings.push(..._useAssetOnFileColumnRules(source));
2877
2904
  findings.push(..._rowActionGatingRules(source));
2878
2905
  // REQ-USERMGMT / REQ-ACL-SYS M3 — scope-aware rules. Run after the
2879
2906
  // line-by-line scan so banned-identifier findings stay first in the
package/dist/linter.js CHANGED
@@ -1579,6 +1579,32 @@ function _rawUseMemoIntoDatastoreQueryRules(source) {
1579
1579
  return findings;
1580
1580
  }
1581
1581
 
1582
+ // A FILE column holds a filestore file id; useAsset resolves asset-library
1583
+ // ids only, so a row value passed to it renders a blank picture.
1584
+ function _useAssetOnFileColumnRules(source) {
1585
+ const code = _stripNonCode(source);
1586
+ if (!/\buseDatastore(?:Query|Record)\w*\s*\(/.test(code)) return [];
1587
+ const callRe = /\buseAsset\s*\(\s*(?:row|record|item)\s*\??\s*[.[]/g;
1588
+ const sourceLines = source.split(/\r?\n/);
1589
+ const lineAt = _lineCounter(code);
1590
+ const findings = [];
1591
+ let m;
1592
+ while ((m = callRe.exec(code))) {
1593
+ const line = lineAt(m.index);
1594
+ findings.push({
1595
+ rule: "useAsset-on-file-column",
1596
+ severity: "warning",
1597
+ label:
1598
+ "useAsset() resolves an asset-library id, never a FILE column's value: " +
1599
+ "that is a filestore file id, so draw it with <FileImage fileId={…}> " +
1600
+ "or read useFilestoreFile(id).url.",
1601
+ line,
1602
+ snippet: (sourceLines[line - 1] || "").trim().slice(0, 200),
1603
+ });
1604
+ }
1605
+ return findings;
1606
+ }
1607
+
1582
1608
  // sc-5206 — soft warning, the write-gate twin of `write-not-gated-on-user`.
1583
1609
  // A widget that hand-derives write permission from `useUser().groupIds` /
1584
1610
  // `.roles` re-implements the table ACL client-side and drifts from it the
@@ -2869,6 +2895,7 @@ export function lintSource(source, options) {
2869
2895
  // sc-5206 — soft warnings steering a widget toward the two new hooks.
2870
2896
  findings.push(..._rawUseMemoIntoDatastoreQueryRules(source));
2871
2897
  findings.push(..._handRolledWriteGateRules(source));
2898
+ findings.push(..._useAssetOnFileColumnRules(source));
2872
2899
  findings.push(..._rowActionGatingRules(source));
2873
2900
  // REQ-USERMGMT / REQ-ACL-SYS M3 — scope-aware rules. Run after the
2874
2901
  // line-by-line scan so banned-identifier findings stay first in the
@@ -2,7 +2,8 @@
2
2
  //
3
3
  // Kept apart from the editor module so the web host can style the field
4
4
  // before the lazily loaded editor arrives, and the native WebView page can
5
- // paint the same rules from the tokens it is sent.
5
+ // paint the same rules from the tokens it is sent. List markers are declared
6
+ // outright: a host stylesheet reset (Tailwind preflight) strips them.
6
7
 
7
8
  // Theme values are written into a stylesheet, so nothing may close the rule.
8
9
  const clean = (value) => String(value == null ? "" : value).replace(/[{}<>;\\]/g, "");
@@ -30,6 +31,8 @@ ${s} .ProseMirror h1{font-size:${px(t.headingSizes[1])};line-height:1.3}
30
31
  ${s} .ProseMirror h2{font-size:${px(t.headingSizes[2])};line-height:1.3}
31
32
  ${s} .ProseMirror h3{font-size:${px(t.headingSizes[3])};line-height:1.3}
32
33
  ${s} .ProseMirror ul,${s} .ProseMirror ol{margin:0 0 ${px(t.blockGap)};padding-left:1.4em}
34
+ ${s} .ProseMirror ul{list-style-type:disc}
35
+ ${s} .ProseMirror ol{list-style-type:decimal}
33
36
  ${s} .ProseMirror li p{margin:0}
34
37
  ${s} .ProseMirror code{font-family:ui-monospace,Menlo,monospace;font-size:${px(t.codeSize)};color:${t.mutedColor}}
35
38
  ${s} .ProseMirror pre{margin:0 0 ${px(t.blockGap)};padding:${px(t.blockGap)};border:1px solid ${t.border};border-radius:${px(t.radius)};white-space:pre-wrap}